Skip to content

Add batch-transfer example app (TypeScript + Go)#21

Merged
gregnazario merged 5 commits intomainfrom
claude/hardcore-bardeen
Feb 23, 2026
Merged

Add batch-transfer example app (TypeScript + Go)#21
gregnazario merged 5 commits intomainfrom
claude/hardcore-bardeen

Conversation

@gregnazario
Copy link
Copy Markdown
Collaborator

Summary

  • Adds examples/batch-transfer/ with reference implementations in TypeScript and Go
  • Teaches the core batch-submission pattern: fetch sequence number once, build + sign all transactions locally, then submit in parallel
  • Includes retry with exponential backoff on confirmation
  • Produces identical JSON summary output across both languages for easy cross-SDK comparison

Key pattern demonstrated

# Naive (N round-trips):
for each tx: fetch seq_num → build → submit

# This example (1 round-trip):
fetch seq_num once
for each tx (local): build with (seq_num + i) → sign
submit all in parallel

Test plan

  • cd examples/batch-transfer/typescript && bun install && bun src/main.ts runs against devnet
  • cd examples/batch-transfer/go && go run main.go runs against devnet
  • Both produce the 4-phase output and JSON summary
  • --count and --network flags work as expected
  • TypeScript: bunx tsc --noEmit passes
  • Go: go vet ./... and go build ./... pass

Demonstrates sending N APT transfers in parallel using the key pattern
of fetching the sender's sequence number once and incrementing locally,
avoiding N network round-trips and enabling true parallel submission.

Features shown:
- Account generation and faucet funding
- Gas price estimation
- Sequence number pre-fetching (fetch once, increment locally)
- Local build + sign before any network submission
- Parallel dispatch (Promise.allSettled / goroutines + WaitGroup)
- Exponential backoff retry on confirmation
- Structured JSON summary output for cross-SDK comparison

CLI: --count N --network devnet|testnet|mainnet (default: devnet, 10 txs)
@gregnazario gregnazario force-pushed the claude/hardcore-bardeen branch from 7ca89f6 to b50ad6b Compare February 21, 2026 22:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new examples/batch-transfer/ reference example demonstrating the “fetch sequence number once, build+sign locally, submit in parallel, then confirm with retry” batch-submission pattern in both TypeScript (ts-sdk) and Go (aptos-go-sdk), with aligned 4-phase console output and a shared JSON summary shape for cross-SDK comparison.

Changes:

  • Add TypeScript batch-transfer app (Bun-based) with parallel submit and confirmation retry.
  • Add Go batch-transfer app with goroutine-based parallel submit and confirmation retry.
  • Add documentation for the batch-transfer example and register it in examples/README.md.

Reviewed changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
examples/batch-transfer/typescript/src/main.ts Implements TS batch build/sign/submit/confirm flow and JSON summary output.
examples/batch-transfer/typescript/package.json Declares Bun runnable example and ts-sdk dependency.
examples/batch-transfer/typescript/tsconfig.json Adds strict TS compilation config for the example.
examples/batch-transfer/typescript/bun.lock Locks Bun dependencies for reproducible installs.
examples/batch-transfer/go/main.go Implements Go batch build/sign/submit/confirm flow and JSON summary output.
examples/batch-transfer/go/go.mod Adds standalone Go module for the example pinned to aptos-go-sdk.
examples/batch-transfer/go/go.sum Locks Go dependency checksums.
examples/batch-transfer/README.md Documents the pattern, usage, expected output, and CLI options.
examples/README.md Adds the new example to the examples index and describes example design principles.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +132 to +136
// Fund the sender via the devnet/testnet faucet
const fundResult = await aptos.fundAccount({
accountAddress: sender.accountAddress,
amount: FUND_AMOUNT_OCTAS,
});
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--network advertises mainnet, but this example always generates a fresh sender and funds it via aptos.fundAccount(), which won't work on mainnet (no faucet). Consider either rejecting mainnet with a clear error, or supporting mainnet by requiring a user-provided funded sender key (env var / flag) and skipping faucet funding.

Copilot uses AI. Check for mistakes.
count := flag.Int("count", 10, "Number of transactions to send")
networkName := flag.String("network", "devnet", "Network: devnet, testnet, or mainnet")
flag.Parse()

Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--count is not validated. A negative value (e.g. --count=-1) will cause make(..., *count) to panic when creating slices later. Consider validating *count >= 1 immediately after flag.Parse() and exiting with a clear error message (to match the TypeScript example’s behavior).

Suggested change
if *count < 1 {
fmt.Fprintf(os.Stderr, "Invalid value for --count: %d (must be >= 1)\n", *count)
os.Exit(1)
}

Copilot uses AI. Check for mistakes.
Comment on lines +165 to +169
if err := client.Fund(sender.Address, fundAmountOctas); err != nil {
fmt.Fprintf(os.Stderr, "Failed to fund sender: %v\n", err)
os.Exit(1)
}
fmt.Printf(" ✓ Funded sender with 1 APT\n")
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--network includes mainnet, but this example always creates a new sender and calls client.Fund(...) (faucet), which won't work on mainnet. Consider rejecting mainnet with a clear error, or adding a mode that uses a user-supplied funded sender account and skips faucet funding.

Copilot uses AI. Check for mistakes.
- Reject --network mainnet with clear error in both TS and Go (example
  uses faucet, which does not exist on mainnet)
- Validate --network against allowed values (devnet|testnet); exit with
  usage error on unknown input instead of silently falling back to devnet
- Validate --count >= 1 in Go to prevent slice-allocation panic
- Guard totalSpent against negative values in TypeScript with Math.max(0,...)
  (Go already had this guard via the uint64 underflow check)
- Handle json.MarshalIndent error in Go instead of discarding it
- Update docs/comments to remove mainnet from advertised options
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 9 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +58
for (let i = 0; i < args.length; i++) {
if (args[i] === "--count" && i + 1 < args.length) {
count = parseInt(args[i + 1], 10);
if (isNaN(count) || count < 1) {
console.error("--count must be a positive integer");
process.exit(1);
}
}
if (args[i] === "--network" && i + 1 < args.length) {
network = args[i + 1];
if (network === "mainnet") {
console.error(
"Error: mainnet is not supported by this example because it funds a new account via faucet.",
);
console.error("To use mainnet, extend this example to accept a pre-funded sender key.");
process.exit(1);
}
if (!["devnet", "testnet"].includes(network)) {
console.error(`Error: unknown network "${network}". Allowed values: devnet, testnet`);
process.exit(1);
}
}
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI argument parsing doesn't skip the argument value after processing a flag. When an argument like "--count 20" is encountered, the loop should increment i to skip "20" after processing it. Otherwise, if someone passes "--count --network devnet", the "--network" will be treated as the count value (which would fail the parseInt), but in general this pattern could cause unexpected behavior. Consider adding "i++" after processing each flag's value, or use a proper argument parsing library.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 9 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@gregnazario gregnazario merged commit 7f945d7 into main Feb 23, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants